Example pair

D_T8_C1R2_1 D_T8_C1R2_2 D_T8_C1R2_5-95_1_2

list_images <- tibble(
    image_name = c("D_T8_C1R2_1", "D_T8_C1R2_2", "D_T8_C1R2_5-95_1_2"),
    folder_original = rep(paste0(folder_main, "check/D-00-original/"), 3),
    folder_green = rep(paste0(folder_main, "example/"), 3),
    folder_rolled = rep(paste0(folder_main, "example/"), 3),
    folder_watershed = rep(paste0(folder_main, "example/"), 3),
    folder_red = rep(paste0(folder_main, "example/"), 3),
    folder_blue = rep(paste0(folder_main, "example/"), 3)
)

i = 3
image_name <- list_images$image_name[i]
folder_original <- list_images$folder_original[i]
folder_green <- list_images$folder_green[i]
folder_red <- list_images$folder_red[i]
folder_blue <- list_images$folder_blue[i]
folder_rolled <- list_images$folder_rolled[i]
folder_watershed <- list_images$folder_rolled[i]

0. original image

image_original <- readImage(paste0(folder_original, image_name, ".tiff"))
writeImage(image_original, paste0(folder_green, image_name, "-00-original.tiff"))
display(image_original, method = "raster")

1. Green channel

temp <- image_original
colorMode(temp) = Grayscale
image_green <- temp[,,2]
writeImage(image_green, paste0(folder_green, image_name, "-01-green.tiff"))
display(image_green, method = "raster")

Red channel

temp <- image_original
colorMode(temp) = Grayscale
image_red <- temp[,,1]
writeImage(image_red, paste0(folder_red, image_name, "-11-red.tiff"))
display(image_red, method = "raster")

Blue channel

temp <- image_original
colorMode(temp) = Grayscale
image_blue <- temp[,,1]
writeImage(image_blue, paste0(folder_blue, image_name, "-21-blue.tiff"))
display(image_blue, method = "raster")

2. Rolling ball

Python code from rolling_ball.py

py$file_green <- paste0(folder_green, image_name, ".tiff")
py$file_rolled <- paste0(folder_rolled, image_name, "-02-rolled.tiff")

The R variables should be passed to python but somehow failed. Not sure where the problem is from, probably reticualte. 20220902 use the externally generated rolled images.

import imageio
import os
import sys
import skimage
from skimage import data, restoration, util, io, color

def rolling_ball_light(image):
    # 2. invert the image
    image_inverted = util.invert(image)
    
    # 3. rolling ball
    background_inverted = restoration.rolling_ball(image_inverted, radius = 80, num_threads = 10)
    
    # 4. invert the result
    image_rolled_inverted = image_inverted - background_inverted
    image_rolled = util.invert(image_rolled_inverted)
    return image_rolled
    
image = io.imread(file_green)
NameError: name 'file_green' is not defined
image_rolled = rolling_ball_light(image)
NameError: name 'image' is not defined
io.imsave(file_rolled, image_rolled)
NameError: name 'file_rolled' is not defined

Display the rolled result

image_rolled <- readImage(paste0(folder_rolled, image_name, "-02-rolled.tiff"))
display(image_rolled, method = "raster")

3. Thresholding

Here because the images have undergone gray scale and background subtraction so I apply a global threshold to the image

threshold <- otsu(image_rolled)
image_thresholded <- image_rolled < threshold
display(image_thresholded, method = "raster")

4. Detect round shaped object and remove super small size

To select potential colonies: area, perimeter, and circularity.

This step is implemented here to prevent over segmentation and reduce segmentation load

As you can see here, attached objects are not divided yet

image_object <- bwlabel(image_thresholded)
object_shape <- computeFeatures.shape(image_object) %>% as_tibble(rownames = "ObjectID")

object_shape_round <- object_shape %>%
    # Area
    filter(s.area > 500 & s.area < 500000) %>%
    # Roundness = 1 means a perfect circle
    mutate(Roundness = (s.radius.max - s.radius.min)/2) %>%
    filter(Roundness > 0.1 & Roundness < 50) %>%
    # Circularity = 1 means a perfect circle and goes down to 0 for non-cicular shapes
    mutate(Circularity = 4 * pi * s.area / s.perimeter^2) %>%
    filter(Circularity > 0.3) %>%
    arrange(desc(s.area))
object_ID_nonround <- which(!(object_shape$ObjectID %in% object_shape_round$ObjectID))

image_round <- rmObjects(image_object, object_ID_nonround, reenumerate = F)
display(colorLabels(image_round), method = "raster")

5. Distance map

The distance map contains for each pixel the distance to the nearest background pixe

image_distancemap <- distmap(image_round)
display(normalize(image_distancemap), method = "raster")

6. Watershed

This is the actual segmentation step. Here the adjacent objects are better distinguished

image_watershed <- watershed(image_distancemap, tolerance = 1)
display(colorLabels(image_watershed), method = "raster")

Output the example

# writeImage(image_original, paste0(folder_main, "example/", image_name, "-00-original.tiff"))
# writeImage(image_green, paste0(folder_main, "example/", image_name, "-01-green.tiff"))
writeImage(image_rolled, paste0(folder_main, "example/", image_name, "-02-rolled.tiff"))
writeImage(image_thresholded, paste0(folder_main, "example/", image_name, "-03-threshold.tiff"))
#writeImage(colorLabels(image_object), paste0(folder_main, "example/", image_name, "-03a-object.tiff"))
writeImage(image_round, paste0(folder_main, "example/", image_name, "-04-round_object.tiff"))
writeImage(normalize(image_distancemap), paste0(folder_main, "example/", image_name, "-05-distance_map.tiff"))
writeImage(colorLabels(image_watershed), paste0(folder_main, "example/", image_name, "-06-watershed.tiff"))
LS0tCnRpdGxlOiAiUiBOb3RlYm9vayIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CiNrbml0cjo6b3B0c19jaHVuaygpCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KEVCSW1hZ2UpCmxpYnJhcnkocmV0aWN1bGF0ZSkKZm9sZGVyX21haW4gPC0gIi9Vc2Vycy9jaGFuZy15dS9Ecm9wYm94L2xhYi9lbWVyZ2VudC1jb2V4aXN0ZW5jZS9kYXRhL3Jhdy9wbGF0ZV9zY2FuL2VtZXJnZW50X2NvZXhpc3RlbmNlX3BsYXRlX3NjYW5fY2hlY2svIgpgYGAKCgpFeGFtcGxlIHBhaXIgCgpEX1Q4X0MxUjJfMQpEX1Q4X0MxUjJfMiAKRF9UOF9DMVIyXzUtOTVfMV8yCgoKYGBge3J9Cmxpc3RfaW1hZ2VzIDwtIHRpYmJsZSgKICAgIGltYWdlX25hbWUgPSBjKCJEX1Q4X0MxUjJfMSIsICJEX1Q4X0MxUjJfMiIsICJEX1Q4X0MxUjJfNS05NV8xXzIiKSwKICAgIGZvbGRlcl9vcmlnaW5hbCA9IHJlcChwYXN0ZTAoZm9sZGVyX21haW4sICJjaGVjay9ELTAwLW9yaWdpbmFsLyIpLCAzKSwKICAgIGZvbGRlcl9ncmVlbiA9IHJlcChwYXN0ZTAoZm9sZGVyX21haW4sICJleGFtcGxlLyIpLCAzKSwKICAgIGZvbGRlcl9yb2xsZWQgPSByZXAocGFzdGUwKGZvbGRlcl9tYWluLCAiZXhhbXBsZS8iKSwgMyksCiAgICBmb2xkZXJfd2F0ZXJzaGVkID0gcmVwKHBhc3RlMChmb2xkZXJfbWFpbiwgImV4YW1wbGUvIiksIDMpLAogICAgZm9sZGVyX3JlZCA9IHJlcChwYXN0ZTAoZm9sZGVyX21haW4sICJleGFtcGxlLyIpLCAzKSwKICAgIGZvbGRlcl9ibHVlID0gcmVwKHBhc3RlMChmb2xkZXJfbWFpbiwgImV4YW1wbGUvIiksIDMpCikKCmkgPSAzCmltYWdlX25hbWUgPC0gbGlzdF9pbWFnZXMkaW1hZ2VfbmFtZVtpXQpmb2xkZXJfb3JpZ2luYWwgPC0gbGlzdF9pbWFnZXMkZm9sZGVyX29yaWdpbmFsW2ldCmZvbGRlcl9ncmVlbiA8LSBsaXN0X2ltYWdlcyRmb2xkZXJfZ3JlZW5baV0KZm9sZGVyX3JlZCA8LSBsaXN0X2ltYWdlcyRmb2xkZXJfcmVkW2ldCmZvbGRlcl9ibHVlIDwtIGxpc3RfaW1hZ2VzJGZvbGRlcl9ibHVlW2ldCmZvbGRlcl9yb2xsZWQgPC0gbGlzdF9pbWFnZXMkZm9sZGVyX3JvbGxlZFtpXQpmb2xkZXJfd2F0ZXJzaGVkIDwtIGxpc3RfaW1hZ2VzJGZvbGRlcl9yb2xsZWRbaV0KYGBgCgojIDAuIG9yaWdpbmFsIGltYWdlCgpgYGB7cn0KaW1hZ2Vfb3JpZ2luYWwgPC0gcmVhZEltYWdlKHBhc3RlMChmb2xkZXJfb3JpZ2luYWwsIGltYWdlX25hbWUsICIudGlmZiIpKQp3cml0ZUltYWdlKGltYWdlX29yaWdpbmFsLCBwYXN0ZTAoZm9sZGVyX2dyZWVuLCBpbWFnZV9uYW1lLCAiLTAwLW9yaWdpbmFsLnRpZmYiKSkKZGlzcGxheShpbWFnZV9vcmlnaW5hbCwgbWV0aG9kID0gInJhc3RlciIpCmBgYAoKIyAxLiBHcmVlbiBjaGFubmVsCgpgYGB7cn0KdGVtcCA8LSBpbWFnZV9vcmlnaW5hbApjb2xvck1vZGUodGVtcCkgPSBHcmF5c2NhbGUKaW1hZ2VfZ3JlZW4gPC0gdGVtcFssLDJdCndyaXRlSW1hZ2UoaW1hZ2VfZ3JlZW4sIHBhc3RlMChmb2xkZXJfZ3JlZW4sIGltYWdlX25hbWUsICItMDEtZ3JlZW4udGlmZiIpKQpkaXNwbGF5KGltYWdlX2dyZWVuLCBtZXRob2QgPSAicmFzdGVyIikKYGBgClJlZCBjaGFubmVsCgpgYGB7cn0KdGVtcCA8LSBpbWFnZV9vcmlnaW5hbApjb2xvck1vZGUodGVtcCkgPSBHcmF5c2NhbGUKaW1hZ2VfcmVkIDwtIHRlbXBbLCwxXQp3cml0ZUltYWdlKGltYWdlX3JlZCwgcGFzdGUwKGZvbGRlcl9yZWQsIGltYWdlX25hbWUsICItMTEtcmVkLnRpZmYiKSkKZGlzcGxheShpbWFnZV9yZWQsIG1ldGhvZCA9ICJyYXN0ZXIiKQpgYGAKCkJsdWUgY2hhbm5lbAoKYGBge3J9CnRlbXAgPC0gaW1hZ2Vfb3JpZ2luYWwKY29sb3JNb2RlKHRlbXApID0gR3JheXNjYWxlCmltYWdlX2JsdWUgPC0gdGVtcFssLDFdCndyaXRlSW1hZ2UoaW1hZ2VfYmx1ZSwgcGFzdGUwKGZvbGRlcl9ibHVlLCBpbWFnZV9uYW1lLCAiLTIxLWJsdWUudGlmZiIpKQpkaXNwbGF5KGltYWdlX2JsdWUsIG1ldGhvZCA9ICJyYXN0ZXIiKQpgYGAKCgoKCiMgMi4gUm9sbGluZyBiYWxsCgpQeXRob24gY29kZSBmcm9tIGByb2xsaW5nX2JhbGwucHlgCgpgYGB7cn0KcHkkZmlsZV9ncmVlbiA8LSBwYXN0ZTAoZm9sZGVyX2dyZWVuLCBpbWFnZV9uYW1lLCAiLnRpZmYiKQpweSRmaWxlX3JvbGxlZCA8LSBwYXN0ZTAoZm9sZGVyX3JvbGxlZCwgaW1hZ2VfbmFtZSwgIi0wMi1yb2xsZWQudGlmZiIpCmBgYAoKVGhlIFIgdmFyaWFibGVzIHNob3VsZCBiZSBwYXNzZWQgdG8gcHl0aG9uIGJ1dCBzb21laG93IGZhaWxlZC4gTm90IHN1cmUgd2hlcmUgdGhlIHByb2JsZW0gaXMgZnJvbSwgcHJvYmFibHkgcmV0aWN1YWx0ZS4gMjAyMjA5MDIgdXNlIHRoZSBleHRlcm5hbGx5IGdlbmVyYXRlZCByb2xsZWQgaW1hZ2VzLgoKYGBge3B5dGhvbn0KaW1wb3J0IGltYWdlaW8KaW1wb3J0IG9zCmltcG9ydCBzeXMKaW1wb3J0IHNraW1hZ2UKZnJvbSBza2ltYWdlIGltcG9ydCBkYXRhLCByZXN0b3JhdGlvbiwgdXRpbCwgaW8sIGNvbG9yCgpkZWYgcm9sbGluZ19iYWxsX2xpZ2h0KGltYWdlKToKICAgICMgMi4gaW52ZXJ0IHRoZSBpbWFnZQogICAgaW1hZ2VfaW52ZXJ0ZWQgPSB1dGlsLmludmVydChpbWFnZSkKICAgIAogICAgIyAzLiByb2xsaW5nIGJhbGwKICAgIGJhY2tncm91bmRfaW52ZXJ0ZWQgPSByZXN0b3JhdGlvbi5yb2xsaW5nX2JhbGwoaW1hZ2VfaW52ZXJ0ZWQsIHJhZGl1cyA9IDgwLCBudW1fdGhyZWFkcyA9IDEwKQogICAgCiAgICAjIDQuIGludmVydCB0aGUgcmVzdWx0CiAgICBpbWFnZV9yb2xsZWRfaW52ZXJ0ZWQgPSBpbWFnZV9pbnZlcnRlZCAtIGJhY2tncm91bmRfaW52ZXJ0ZWQKICAgIGltYWdlX3JvbGxlZCA9IHV0aWwuaW52ZXJ0KGltYWdlX3JvbGxlZF9pbnZlcnRlZCkKICAgIHJldHVybiBpbWFnZV9yb2xsZWQKICAgIAppbWFnZSA9IGlvLmltcmVhZChmaWxlX2dyZWVuKQppbWFnZV9yb2xsZWQgPSByb2xsaW5nX2JhbGxfbGlnaHQoaW1hZ2UpCmlvLmltc2F2ZShmaWxlX3JvbGxlZCwgaW1hZ2Vfcm9sbGVkKQpgYGAKCkRpc3BsYXkgdGhlIHJvbGxlZCByZXN1bHQKCmBgYHtyfQppbWFnZV9yb2xsZWQgPC0gcmVhZEltYWdlKHBhc3RlMChmb2xkZXJfcm9sbGVkLCBpbWFnZV9uYW1lLCAiLTAyLXJvbGxlZC50aWZmIikpCmRpc3BsYXkoaW1hZ2Vfcm9sbGVkLCBtZXRob2QgPSAicmFzdGVyIikKYGBgCgoKCiMgMy4gVGhyZXNob2xkaW5nCgpIZXJlIGJlY2F1c2UgdGhlIGltYWdlcyBoYXZlIHVuZGVyZ29uZSBncmF5IHNjYWxlIGFuZCBiYWNrZ3JvdW5kIHN1YnRyYWN0aW9uIHNvIEkgYXBwbHkgYSBnbG9iYWwgdGhyZXNob2xkIHRvIHRoZSBpbWFnZQoKYGBge3J9CnRocmVzaG9sZCA8LSBvdHN1KGltYWdlX3JvbGxlZCkKaW1hZ2VfdGhyZXNob2xkZWQgPC0gaW1hZ2Vfcm9sbGVkIDwgdGhyZXNob2xkCmRpc3BsYXkoaW1hZ2VfdGhyZXNob2xkZWQsIG1ldGhvZCA9ICJyYXN0ZXIiKQpgYGAKCiMgNC4gRGV0ZWN0IHJvdW5kIHNoYXBlZCBvYmplY3QgYW5kIHJlbW92ZSBzdXBlciBzbWFsbCBzaXplCgpUbyBzZWxlY3QgcG90ZW50aWFsIGNvbG9uaWVzOiBhcmVhLCBwZXJpbWV0ZXIsIGFuZCBjaXJjdWxhcml0eS4KClRoaXMgc3RlcCBpcyBpbXBsZW1lbnRlZCBoZXJlIHRvIHByZXZlbnQgb3ZlciBzZWdtZW50YXRpb24gYW5kIHJlZHVjZSBzZWdtZW50YXRpb24gbG9hZAoKQXMgeW91IGNhbiBzZWUgaGVyZSwgYXR0YWNoZWQgb2JqZWN0cyBhcmUgbm90IGRpdmlkZWQgeWV0CgpgYGB7cn0KaW1hZ2Vfb2JqZWN0IDwtIGJ3bGFiZWwoaW1hZ2VfdGhyZXNob2xkZWQpCm9iamVjdF9zaGFwZSA8LSBjb21wdXRlRmVhdHVyZXMuc2hhcGUoaW1hZ2Vfb2JqZWN0KSAlPiUgYXNfdGliYmxlKHJvd25hbWVzID0gIk9iamVjdElEIikKCm9iamVjdF9zaGFwZV9yb3VuZCA8LSBvYmplY3Rfc2hhcGUgJT4lCiAgICAjIEFyZWEKICAgIGZpbHRlcihzLmFyZWEgPiA1MDAgJiBzLmFyZWEgPCA1MDAwMDApICU+JQogICAgIyBSb3VuZG5lc3MgPSAxIG1lYW5zIGEgcGVyZmVjdCBjaXJjbGUKICAgIG11dGF0ZShSb3VuZG5lc3MgPSAocy5yYWRpdXMubWF4IC0gcy5yYWRpdXMubWluKS8yKSAlPiUKICAgIGZpbHRlcihSb3VuZG5lc3MgPiAwLjEgJiBSb3VuZG5lc3MgPCA1MCkgJT4lCiAgICAjIENpcmN1bGFyaXR5ID0gMSBtZWFucyBhIHBlcmZlY3QgY2lyY2xlIGFuZCBnb2VzIGRvd24gdG8gMCBmb3Igbm9uLWNpY3VsYXIgc2hhcGVzCiAgICBtdXRhdGUoQ2lyY3VsYXJpdHkgPSA0ICogcGkgKiBzLmFyZWEgLyBzLnBlcmltZXRlcl4yKSAlPiUKICAgIGZpbHRlcihDaXJjdWxhcml0eSA+IDAuMykgJT4lCiAgICBhcnJhbmdlKGRlc2Mocy5hcmVhKSkKb2JqZWN0X0lEX25vbnJvdW5kIDwtIHdoaWNoKCEob2JqZWN0X3NoYXBlJE9iamVjdElEICVpbiUgb2JqZWN0X3NoYXBlX3JvdW5kJE9iamVjdElEKSkKCmltYWdlX3JvdW5kIDwtIHJtT2JqZWN0cyhpbWFnZV9vYmplY3QsIG9iamVjdF9JRF9ub25yb3VuZCwgcmVlbnVtZXJhdGUgPSBGKQpkaXNwbGF5KGNvbG9yTGFiZWxzKGltYWdlX3JvdW5kKSwgbWV0aG9kID0gInJhc3RlciIpCmBgYAoKIyA1LiBEaXN0YW5jZSBtYXAKClRoZSBkaXN0YW5jZSBtYXAgY29udGFpbnMgZm9yIGVhY2ggcGl4ZWwgdGhlIGRpc3RhbmNlIHRvIHRoZSBuZWFyZXN0IGJhY2tncm91bmQgcGl4ZQoKCmBgYHtyfQppbWFnZV9kaXN0YW5jZW1hcCA8LSBkaXN0bWFwKGltYWdlX3JvdW5kKQpkaXNwbGF5KG5vcm1hbGl6ZShpbWFnZV9kaXN0YW5jZW1hcCksIG1ldGhvZCA9ICJyYXN0ZXIiKQpgYGAKCiMgNi4gV2F0ZXJzaGVkCgpUaGlzIGlzIHRoZSBhY3R1YWwgc2VnbWVudGF0aW9uIHN0ZXAuIEhlcmUgdGhlIGFkamFjZW50IG9iamVjdHMgYXJlIGJldHRlciBkaXN0aW5ndWlzaGVkCgpgYGB7cn0KaW1hZ2Vfd2F0ZXJzaGVkIDwtIHdhdGVyc2hlZChpbWFnZV9kaXN0YW5jZW1hcCwgdG9sZXJhbmNlID0gMSkKZGlzcGxheShjb2xvckxhYmVscyhpbWFnZV93YXRlcnNoZWQpLCBtZXRob2QgPSAicmFzdGVyIikKYGBgCgoKCiMgT3V0cHV0IHRoZSBleGFtcGxlCgpgYGB7cn0KIyAjIHdyaXRlSW1hZ2UoaW1hZ2Vfb3JpZ2luYWwsIHBhc3RlMChmb2xkZXJfbWFpbiwgImV4YW1wbGUvIiwgaW1hZ2VfbmFtZSwgIi0wMC1vcmlnaW5hbC50aWZmIikpCiMgIyB3cml0ZUltYWdlKGltYWdlX2dyZWVuLCBwYXN0ZTAoZm9sZGVyX21haW4sICJleGFtcGxlLyIsIGltYWdlX25hbWUsICItMDEtZ3JlZW4udGlmZiIpKQojIHdyaXRlSW1hZ2UoaW1hZ2Vfcm9sbGVkLCBwYXN0ZTAoZm9sZGVyX21haW4sICJleGFtcGxlLyIsIGltYWdlX25hbWUsICItMDItcm9sbGVkLnRpZmYiKSkKIyB3cml0ZUltYWdlKGltYWdlX3RocmVzaG9sZGVkLCBwYXN0ZTAoZm9sZGVyX21haW4sICJleGFtcGxlLyIsIGltYWdlX25hbWUsICItMDMtdGhyZXNob2xkLnRpZmYiKSkKIyAjd3JpdGVJbWFnZShjb2xvckxhYmVscyhpbWFnZV9vYmplY3QpLCBwYXN0ZTAoZm9sZGVyX21haW4sICJleGFtcGxlLyIsIGltYWdlX25hbWUsICItMDNhLW9iamVjdC50aWZmIikpCiMgd3JpdGVJbWFnZShpbWFnZV9yb3VuZCwgcGFzdGUwKGZvbGRlcl9tYWluLCAiZXhhbXBsZS8iLCBpbWFnZV9uYW1lLCAiLTA0LXJvdW5kX29iamVjdC50aWZmIikpCiMgd3JpdGVJbWFnZShub3JtYWxpemUoaW1hZ2VfZGlzdGFuY2VtYXApLCBwYXN0ZTAoZm9sZGVyX21haW4sICJleGFtcGxlLyIsIGltYWdlX25hbWUsICItMDUtZGlzdGFuY2VfbWFwLnRpZmYiKSkKIyB3cml0ZUltYWdlKGNvbG9yTGFiZWxzKGltYWdlX3dhdGVyc2hlZCksIHBhc3RlMChmb2xkZXJfbWFpbiwgImV4YW1wbGUvIiwgaW1hZ2VfbmFtZSwgIi0wNi13YXRlcnNoZWQudGlmZiIpKQpgYGAKCgoKCgoKCgoKCgoK